/////////////////////////////////////////////////////////////////////////////
// DynamicDialogTemplate.h - Class used to construct an in-memory dialog
//   template, along with controls to go on the dialog
//
// Written by Daniel Bowen (dbowen@es.com)
// Copyright (c) 2003-2004 Daniel Bowen.
//
// This code may be used in compiled form in any way you desire. This
// file may be redistributed by any means PROVIDING it is
// not sold for profit without the authors written consent, and
// providing that this notice and the authors name is included.
//
// This file is provided "as is" with no expressed or implied warranty.
// The author accepts no liability if it causes any damage to you or your
// computer whatsoever.
//
// If you find bugs, have suggestions for improvements, etc.,
// please contact the author.
//
//
//  CDynamicDialogTemplate - Used to construct an in-memory dialog template
//    based on DLGTEMPLATE and DLGITEMTEMPLATE
//
//  CDynamicDialogExTemplate - Used to construct an in-memory dialog template
//    based on DLGTEMPLATEEX and DLGITEMTEMPLATEEX

#ifndef __DynamicDialogTemplate_h__
#define __DynamicDialogTemplate_h__

// To find out more about in-memory dialog templates, see the MSDN library
// for a description of DLGTEMPLATE and DLGITEMTEMPLATE

namespace DynamicDialog {

struct DynamicDialogItemSize
{
public:
	short x, y, cx, cy;

	DynamicDialogItemSize(short left = 0, short top = 0, short width = 0, short height = 0) :
		x(left), y(top), cx(width), cy(height)
	{
	}

	void Set(short left, short top, short width, short height)
	{
		x = left, y = top, cx = width, cy = height;
	}
	void Set(RECT rect)
	{
		x = (short)rect.left;
		y = (short)rect.top;
		cx = (short)(rect.right - rect.left);
		cy = (short)(rect.bottom - rect.top);
	}
};

class CDynamicDialogTemplate
{
protected:
	HGLOBAL m_hDialogTemplateMemory;
	size_t m_bytesAllocated;
	size_t m_bytesUsed;

protected:
	enum
	{
		byteAllocationChunk = 256
	};

	// See help on DLGITEMTEMPLATE
	enum ClassAtom
	{
		eClassAtom_Button = 0x0080,
		eClassAtom_Edit = 0x0081,
		eClassAtom_Static = 0x0082,
		eClassAtom_ListBox = 0x0083,
		eClassAtom_ScrollBar = 0x0084,
		eClassAtom_ComboBox = 0x0085,
	};

	// Constructor/Destructor
public:
	CDynamicDialogTemplate() :
		m_hDialogTemplateMemory(NULL),
		m_bytesAllocated(0),
		m_bytesUsed(0)
	{
	}

	virtual ~CDynamicDialogTemplate()
	{
		this->Destroy();
	}

	// Operators
public:
	operator bool() { return (m_hDialogTemplateMemory != NULL); }
	operator HGLOBAL() { return this->m_hDialogTemplateMemory; }
	operator DLGTEMPLATE*() { return this->GetDLGTEMPLATE(); }

	HGLOBAL GetHGLOBAL(void)
	{
		return this->m_hDialogTemplateMemory;
	}

	DLGTEMPLATE* GetDLGTEMPLATE(void)
	{
		DLGTEMPLATE* dialogTemplate = (DLGTEMPLATE*)::GlobalLock(m_hDialogTemplateMemory);
		::GlobalUnlock(m_hDialogTemplateMemory);

		// This should be valid at least until something causes a GlobalReAlloc.
		// No one should call this (explicitly or implictly) until the dialog
		// resource has been fully constructed
		return dialogTemplate;
	}

public:
	bool Create(
		UINT cxDLU, UINT cyDLU,
		UINT style, UINT styleEx,
		const wchar_t* dialogTitle,
		const wchar_t* fontName = L"MS Sans Serif",
		UINT fontSize = 8)
	{
		if (m_hDialogTemplateMemory)
		{
			return false;
		}

		m_hDialogTemplateMemory = ::GlobalAlloc((GMEM_MOVEABLE | GMEM_ZEROINIT), byteAllocationChunk);
		if (m_hDialogTemplateMemory)
		{
			m_bytesAllocated = byteAllocationChunk;

			DLGTEMPLATE* pDialogTemplate = (DLGTEMPLATE*)::GlobalLock(m_hDialogTemplateMemory);
			if (pDialogTemplate)
			{
				// Define dialog template
				pDialogTemplate->style = style;
				pDialogTemplate->dwExtendedStyle = styleEx;
				pDialogTemplate->cdit = 0;
				pDialogTemplate->x = 0;
				pDialogTemplate->y = 0;
				pDialogTemplate->cx = (short)cxDLU;
				pDialogTemplate->cy = (short)cyDLU;

				// Get a pointer to the WORD after the structure
				WORD* pWordPtr = (WORD*)(pDialogTemplate + 1);

				// no menu
				*pWordPtr++ = 0;
				// default dialog box class
				*pWordPtr++ = 0;

				// Dialog title
				if (dialogTitle)
				{
					wchar_t* unicodeString = (wchar_t*)pWordPtr;
					// dialog caption
					lstrcpyW(unicodeString, dialogTitle);
					pWordPtr += ::lstrlenW(unicodeString);
				}
				// NUL termination for string
				*pWordPtr++ = 0;

				if ((style & DS_SETFONT) == DS_SETFONT)
				{
					// font size
					*pWordPtr++ = (WORD)fontSize;

					if (fontName)
					{
						wchar_t* unicodeString = (wchar_t*)pWordPtr;
						// font name
						lstrcpyW(unicodeString, fontName);
						pWordPtr += ::lstrlenW(unicodeString);
					}

					// NUL termination for string
					*pWordPtr++ = 0;
				}

				m_bytesUsed = (size_t)((BYTE*)pWordPtr - (BYTE*)pDialogTemplate);

				::GlobalUnlock(m_hDialogTemplateMemory);
			}
		}

		return this->m_hDialogTemplateMemory ? true : false;
	}

	void Destroy(void)
	{
		if (m_hDialogTemplateMemory)
		{
			::GlobalFree(m_hDialogTemplateMemory);
			m_hDialogTemplateMemory = NULL;
			m_bytesAllocated = 0;
			m_bytesUsed = 0;
		}
	}

	bool AddControl(
		DWORD style, DWORD dwExtendedStyle,
		short x, short y, short cx, short cy,
		WORD id,
		const wchar_t* text, size_t textWordCount,
		const wchar_t* classStringOrAtom, size_t classStringOrAtomWordCount)
	{
		if (m_hDialogTemplateMemory == NULL)
		{
			return false;
		}

		// padding (class atom, NULL termination, etc.)
		const size_t padding = 16;

		size_t estimatedBytesNeeded = (size_t)sizeof(DLGITEMTEMPLATE) +
									  (textWordCount * sizeof(WORD)) +
									  (classStringOrAtomWordCount * sizeof(WORD)) +
									  padding;

		if ((m_bytesAllocated - this->m_bytesUsed) < estimatedBytesNeeded)
		{
			// Align the bytesToAllocate to be a multiple of the chunk size
			size_t bytesToAllocate = (((m_bytesUsed + estimatedBytesNeeded) / byteAllocationChunk) * byteAllocationChunk) + byteAllocationChunk;

			HGLOBAL hDialogTemplateReallocatedMemory = ::GlobalReAlloc(m_hDialogTemplateMemory, bytesToAllocate, (GMEM_MOVEABLE | GMEM_ZEROINIT));
			if (hDialogTemplateReallocatedMemory == NULL)
			{
				return false;
			}
			else
			{
				m_bytesAllocated = bytesToAllocate;
				m_hDialogTemplateMemory = hDialogTemplateReallocatedMemory;
			}
		}

		DLGTEMPLATE* pDialogTemplate = (DLGTEMPLATE*)::GlobalLock(m_hDialogTemplateMemory);
		if (pDialogTemplate)
		{
			// Increment the control count
			pDialogTemplate->cdit += 1;

			// Get a pointer to the "end"
			BYTE* pOffset = (BYTE*)pDialogTemplate + this->m_bytesUsed;

			// Fill out the structure and following bytes for the control.
			// DLGITEMTEMPLATE structures should be aligned on DWORD boundaries.
			DLGITEMTEMPLATE* pDialogItem = (DLGITEMTEMPLATE*)(((DWORD_PTR)pOffset + 3) & ~3);
			pDialogItem->style = style;
			pDialogItem->dwExtendedStyle = dwExtendedStyle;
			pDialogItem->x = x;
			pDialogItem->y = y;
			pDialogItem->cx = cx;
			pDialogItem->cy = cy;
			pDialogItem->id = id;

			// Get a pointer to the WORD after the structure
			WORD* pWordPtr = (WORD*)(pDialogItem + 1);

			// Class Array (string or atom)
			if (classStringOrAtom)
			{
				::CopyMemory(pWordPtr, classStringOrAtom, classStringOrAtomWordCount * sizeof(WORD));
				pWordPtr += classStringOrAtomWordCount;

				// NOTE! "classStringOrAtomWordCount" should include the
				//  terminating NUL (if its not an atom value)
			}
			else
			{
				// Default to "Static" control if no control class or atom is provided
				*pWordPtr++ = 0xFFFF;
				*pWordPtr++ = eClassAtom_Static;
			}

			// Title Array (text or resource id)
			if (text)
			{
				::CopyMemory(pWordPtr, text, textWordCount * sizeof(WORD));
				pWordPtr += textWordCount;

				// NOTE! "textWordCount" should include the
				//  terminating NUL (if its not a resource ID value)
			}
			else
			{
				// default to 0 length string
				*pWordPtr++ = 0;
			}

			// no creation data
			*pWordPtr++ = 0;

			m_bytesUsed = (size_t)((BYTE*)pWordPtr - (BYTE*)pDialogTemplate);

			::GlobalUnlock(m_hDialogTemplateMemory);
		}

		return true;
	}

	bool AddControl(
		DWORD style, DWORD dwExtendedStyle,
		short x, short y, short cx, short cy,
		WORD id, const wchar_t* text,
		const wchar_t* classString)
	{
		return this->AddControl(style, dwExtendedStyle,
			x, y, cx, cy,
			id,
			text, text ? ::lstrlenW(text) + 1 : 0,
			classString, classString ? ::lstrlenW(classString) + 1 : 0);
	}
	bool AddControl(
		DWORD style, DWORD dwExtendedStyle,
		const DynamicDialogItemSize dialogItemSize,
		WORD id, const wchar_t* text,
		const wchar_t* classString)
	{
		return this->AddControl(style, dwExtendedStyle,
			dialogItemSize.x, dialogItemSize.y, dialogItemSize.cx, dialogItemSize.cy,
			id,
			text, text ? ::lstrlenW(text) + 1 : 0,
			classString, classString ? ::lstrlenW(classString) + 1 : 0);
	}

	bool AddControl(
		DWORD style, DWORD dwExtendedStyle,
		short x, short y, short cx, short cy,
		WORD id, const wchar_t* text,
		ClassAtom atom)
	{
		const long classAtomWordCount = 2;
		WORD classAtom[classAtomWordCount] = {0xFFFF, (WORD)atom};
		return this->AddControl(style, dwExtendedStyle,
			x, y, cx, cy,
			id,
			text, text ? ::lstrlenW(text) + 1 : 0,
			(const wchar_t*)classAtom, classAtomWordCount);
	}
	bool AddControl(
		DWORD style, DWORD dwExtendedStyle,
		const DynamicDialogItemSize dialogItemSize,
		WORD id, const wchar_t* text,
		ClassAtom atom)
	{
		const long classAtomWordCount = 2;
		WORD classAtom[classAtomWordCount] = {0xFFFF, (WORD)atom};
		return this->AddControl(style, dwExtendedStyle,
			dialogItemSize.x, dialogItemSize.y, dialogItemSize.cx, dialogItemSize.cy,
			id,
			text, text ? ::lstrlenW(text) + 1 : 0,
			(const wchar_t*)classAtom, classAtomWordCount);
	}

	bool AddButtonControl(
		DWORD style, DWORD dwExtendedStyle,
		short x, short y, short cx, short cy,
		WORD id, const wchar_t* text)
	{
		return this->AddControl(style, dwExtendedStyle, x, y, cx, cy,
			id, text, eClassAtom_Button);
	}
	bool AddButtonControl(
		DWORD style, DWORD dwExtendedStyle,
		const DynamicDialogItemSize dialogItemSize,
		WORD id, const wchar_t* text)
	{
		return this->AddControl(style, dwExtendedStyle, dialogItemSize,
			id, text, eClassAtom_Button);
	}

	bool AddEditControl(
		DWORD style, DWORD dwExtendedStyle,
		short x, short y, short cx, short cy,
		WORD id, const wchar_t* text)
	{
		return this->AddControl(style, dwExtendedStyle, x, y, cx, cy,
			id, text, eClassAtom_Edit);
	}
	bool AddEditControl(
		DWORD style, DWORD dwExtendedStyle,
		const DynamicDialogItemSize dialogItemSize,
		WORD id, const wchar_t* text)
	{
		return this->AddControl(style, dwExtendedStyle, dialogItemSize,
			id, text, eClassAtom_Edit);
	}

	bool AddStaticControl(
		DWORD style, DWORD dwExtendedStyle,
		short x, short y, short cx, short cy,
		WORD id, const wchar_t* text)
	{
		return this->AddControl(style, dwExtendedStyle, x, y, cx, cy,
			id, text, eClassAtom_Static);
	}
	bool AddStaticControl(
		DWORD style, DWORD dwExtendedStyle,
		const DynamicDialogItemSize dialogItemSize,
		WORD id, const wchar_t* text)
	{
		return this->AddControl(style, dwExtendedStyle, dialogItemSize,
			id, text, eClassAtom_Static);
	}

	bool AddListBoxControl(
		DWORD style, DWORD dwExtendedStyle,
		short x, short y, short cx, short cy,
		WORD id, const wchar_t* text)
	{
		return this->AddControl(style, dwExtendedStyle, x, y, cx, cy,
			id, text, eClassAtom_ListBox);
	}
	bool AddListBoxControl(
		DWORD style, DWORD dwExtendedStyle,
		const DynamicDialogItemSize dialogItemSize,
		WORD id, const wchar_t* text)
	{
		return this->AddControl(style, dwExtendedStyle, dialogItemSize,
			id, text, eClassAtom_ListBox);
	}

	bool AddScrollBarControl(
		DWORD style, DWORD dwExtendedStyle,
		short x, short y, short cx, short cy,
		WORD id, const wchar_t* text)
	{
		return this->AddControl(style, dwExtendedStyle, x, y, cx, cy,
			id, text, eClassAtom_ScrollBar);
	}
	bool AddScrollBarControl(
		DWORD style, DWORD dwExtendedStyle,
		const DynamicDialogItemSize dialogItemSize,
		WORD id, const wchar_t* text)
	{
		return this->AddControl(style, dwExtendedStyle, dialogItemSize,
			id, text, eClassAtom_ScrollBar);
	}

	bool AddComboBoxControl(
		DWORD style, DWORD dwExtendedStyle,
		short x, short y, short cx, short cy,
		WORD id, const wchar_t* text)
	{
		return this->AddControl(style, dwExtendedStyle, x, y, cx, cy,
			id, text, eClassAtom_ComboBox);
	}
	bool AddComboBoxControl(
		DWORD style, DWORD dwExtendedStyle,
		const DynamicDialogItemSize dialogItemSize,
		WORD id, const wchar_t* text)
	{
		return this->AddControl(style, dwExtendedStyle, dialogItemSize,
			id, text, eClassAtom_ComboBox);
	}
};

class CDynamicDialogExTemplate
{
};

// CDynamicDialogImpl is just like CDialogImpl, but it uses CDynamicDialogTemplate, and
//  DialogBoxIndirectParam instead of DialogBoxParam and
//  CreateDialogIndirectParam instead of CreateDialogParam

#if (_ATL_VER >= 0x0700)

// Important!  If ATL::CDialogImpl ever changes, reflect those changes here.
//  We don't inherit from CDialogImpl at all and completely duplicate (and
//  appropriately modify) everything it does.
template <class T, class TBase = ATL::CWindow, class TDynamicDialogTemplate = CDynamicDialogTemplate>
class ATL_NO_VTABLE CDynamicDialogImpl : public ATL::CDialogImplBaseT<TBase>
{
protected:
	TDynamicDialogTemplate m_dynamicDialogTemplate;

	// Overrideables
public:
	// You always need to override ConstructDialogResource
	//bool ConstructDialogResource(void) { }

public:
#ifdef _DEBUG
	bool m_bModal;
	CDynamicDialogImpl() :
		m_bModal(false) {}
#endif //_DEBUG
	// modal dialogs
	INT_PTR DoModal(HWND hWndParent = ::GetActiveWindow(), LPARAM dwInitParam = NULL)
	{
		ATLASSERT(this->m_hWnd == NULL);

		T* pT = static_cast<T*>(this);
		pT->ConstructDialogResource();

		ATLASSERT((bool)m_dynamicDialogTemplate);
		_AtlWinModule.AddCreateWndData(&m_thunk.cd, (ATL::CDialogImplBaseT<TBase>*)this);
#ifdef _DEBUG
		m_bModal = true;
#endif //_DEBUG                                                                                                  \
	//return ::DialogBoxParam(_AtlBaseModule.GetResourceInstance(), MAKEINTRESOURCE(static_cast<T*>(this)->IDD), \
	//			hWndParent, T::StartDialogProc, dwInitParam);
		return ::DialogBoxIndirectParam(_AtlBaseModule.GetResourceInstance(), this->m_dynamicDialogTemplate,
			hWndParent, T::StartDialogProc, dwInitParam);
	}
	BOOL EndDialog(int nRetCode)
	{
		ATLASSERT(::IsWindow(this->m_hWnd));
		ATLASSERT(m_bModal); // must be a modal dialog
		return ::EndDialog(this->m_hWnd, nRetCode);
	}
	// modeless dialogs
	HWND Create(HWND hWndParent, LPARAM dwInitParam = NULL)
	{
		ATLASSERT(this->m_hWnd == NULL);

		T* pT = static_cast<T*>(this);
		pT->ConstructDialogResource();

		ATLASSERT((bool)m_dynamicDialogTemplate);
		_AtlWinModule.AddCreateWndData(&m_thunk.cd, (ATL::CDialogImplBaseT<TBase>*)this);
#ifdef _DEBUG
		m_bModal = false;
#endif //_DEBUG                                                                                                          \
	//HWND hWnd = ::CreateDialogParam(_AtlBaseModule.GetResourceInstance(), MAKEINTRESOURCE(static_cast<T*>(this)->IDD), \
	//			hWndParent, T::StartDialogProc, dwInitParam);
		HWND hWnd = ::CreateDialogIndirectParam(_AtlBaseModule.GetResourceInstance(), this->m_dynamicDialogTemplate,
			hWndParent, T::StartDialogProc, dwInitParam);
		ATLASSERT(this->m_hWnd == hWnd);
		return hWnd;
	}
	// for CComControl
	HWND Create(HWND hWndParent, RECT&, LPARAM dwInitParam = NULL)
	{
		return Create(hWndParent, dwInitParam);
	}
	BOOL DestroyWindow()
	{
		ATLASSERT(::IsWindow(this->m_hWnd));
		ATLASSERT(!m_bModal); // must not be a modal dialog
		return ::DestroyWindow(this->m_hWnd);
	}
};

#endif // (_ATL_VER >= 0x0700)


// CDynamicPropertyPageImpl inherits from CPropertyPageImpl, but it uses CDynamicDialogTemplate
//  to construct an in-memory dialog resource (instead of using a dialog template whose resource
//  identifier is aliased by "IDD" in the derived class).  This in-memory dialog resource is created
//  either during a "Create" call, or when there's an implicit or explict cast to PROPSHEETPAGE*
//  (such as when "AddPage" is called on the sheet with the page as the argument).

template <class T, class TBase = WTL::CPropertyPageWindow, class TDynamicDialogTemplate = CDynamicDialogTemplate>
class ATL_NO_VTABLE CDynamicPropertyPageImpl : public WTL::CPropertyPageImpl<T, TBase>
{
protected:
	typedef WTL::CPropertyPageImpl<T, TBase> baseClass;

protected:
	bool this->m_dialogResourceInitialized;
	TDynamicDialogTemplate this->m_dynamicDialogTemplate;

	// Constructors
public:
	CDynamicPropertyPageImpl(ATL::_U_STRINGorID title = (LPCTSTR)NULL) :
		baseClass(title),
		m_dialogResourceInitialized(false)
	{
		// We do this after the constructor but before
		// the property page is created.
		//T* pT = static_cast<T*>(this);
		//pT->ConstructDialogResource();
		//m_psp.dwFlags |= PSP_DLGINDIRECT;
		//m_psp.pResource = this->m_dynamicDialogTemplate;
	}

	// Enumerations
public:
	// Since we're going to provide the dialog template dynamically,
	// have IDD be 0.  We're still going to inherit from CPropertyPageImpl
	// so this will be used in its constructor (but we'll change things
	// in InitializeDialogResource so "pResource" is used instead of "pszTemplate").
	enum
	{
		IDD = 0
	};

	// CPropertyPageImpl Overrides:
public:
	// This Create() isn't called by WTL at all, but just in case someone else
	// calls it, we need to override it to ensure the dialog resource is initialized.
	HPROPSHEETPAGE Create()
	{
		T* pT = static_cast<T*>(this);
		pT->InitializeDialogResource();

		return baseClass::Create();
	}

public:
	// We'll do a post-constructor construction by overriding the
	//  operator PROPSHEETPAGE*(), and calling "ConstructDialogResource"
	//  which is overrideable.  This should get called when you do
	//  "AddPage", "InsertPage", . The reason we need ConstructDialogResource
	//  to be called outside of the constructor is because we want it
	//  to be overrideable, and have the version in the most derived class called.
	//  In the case of a normal dialog, we can override DoModal and Create.
	//  However, with a property page, those functions don't get called
	//  (they do get called by the sheet though, so something might be added
	//  there to call some kind of "FinalConstruct" like with COM).
	//operator PROPSHEETPAGE*() { return &m_psp; }
	operator PROPSHEETPAGE*()
	{
		T* pT = static_cast<T*>(this);
		pT->InitializeDialogResource();

		return baseClass::operator PROPSHEETPAGE*();
	}

	// Overrideables
public:
	// You always need to override ConstructDialogResource
	//bool ConstructDialogResource(void) { }

	void InitializeDialogResource(void)
	{
		if (!m_dialogResourceInitialized)
		{
			m_dialogResourceInitialized = true;

			T* pT = static_cast<T*>(this);
			if (pT->ConstructDialogResource())
			{
				m_psp.dwFlags |= PSP_DLGINDIRECT;
				m_psp.pResource = this->m_dynamicDialogTemplate;
			}
		}
	}
};

}; // namespace DynamicDialog


#endif //__DynamicDialogTemplate_h__
